package org.moonlight.jvm.instructions.references;

import org.moonlight.jvm.instructions.base.NoOperandsInstruction;
import org.moonlight.jvm.natives.java.lang.throwable.Throwable;
import org.moonlight.jvm.rtda.exclusive.Frame;
import org.moonlight.jvm.rtda.exclusive.OperandStack;
import org.moonlight.jvm.rtda.exclusive.Thread;
import org.moonlight.jvm.rtda.share.Object;
import org.moonlight.jvm.rtda.share.methodarea.StringPool;

/**
 * A_THROW: throw 一个 exception 或 error, 属于引用类指令. 操作数是一个异常对象引用，从操作数栈弹出
 *
 * @author Moonlight
 * @createTime 2023/11/10 9:00
 **/
public class AThrow extends NoOperandsInstruction {

    @Override
    public void execute(Frame frame) {
        Object ex = frame.getOperandStack().popRef();
        if (ex == null) {
            throw new NullPointerException();
        }
        Thread thread = frame.getThread();
        // 如果遍历完Java虚拟机栈还是找不到异常处理代码，则handleUncaughtException()函数打印出Java虚拟机栈信息
        if (!findAndGotoExceptionHandler(thread, ex)) {
            handleUncaughtException(thread, ex);
        }
    }

    private boolean findAndGotoExceptionHandler(Thread thread, Object ex) {
        do {
            // 从当前帧开始，遍历Java虚拟机栈，查找方法的异常处理表。
            // 假设遍历到帧F，如果在F对应的方法中找不到异常处理项，则把F弹出，继续遍历。
            // 反之如果找到了异常处理项，在跳转到异常处理代码之前，要先把F的操作数栈清空，然后把异常对象引用推入栈顶。
            Frame curFrame = thread.currentFrame();
            int pc = curFrame.getNextPc() - 1;

            int handlerPc = curFrame.getMethod().findExceptionHandler(ex.getClazz(), pc);
            if (handlerPc > 0) {
                OperandStack operandStack = curFrame.getOperandStack();
                // 要先把F的操作数栈清空
                operandStack.clear();
                // 然后把异常对象引用推入栈顶
                operandStack.pushRef(ex);
                curFrame.setNextPc(handlerPc);

                return true;
            }
            // 如果在F对应的方法中找不到异常处理项，则把F弹出，继续遍历
            thread.popFrame();
        } while (!thread.isStackEmpty());
        return false;
    }

    private void handleUncaughtException(Thread thread, Object ex) {
        thread.clearStack();

        Object detailMessage = ex.getRefVal("detailMessage", "Ljava/lang/String;");
        String javaString = StringPool.javaString(detailMessage);

        System.out.println(ex.getClazz().getJavaName() + ": " + javaString);

        java.lang.Object extra = ex.getExtra();

        Throwable[] throwableArr = (Throwable[]) extra;
        for (Throwable t : throwableArr) {
            System.out.println("\t at " + t.string());
        }
    }

}
